import re

import lldb

from ..common import (
    SummaryRecognizer,
    SynthRecognizer,
    dereference_value,
    error_ctx,
    get_parent_type_by_name,
)
from ..synth_value import SynthMember, SynthValue


class AstNode(SynthRecognizer, SummaryRecognizer):
    NS = 'ark::es2panda::ir'
    TYPE_ATS_NODE = f'{NS}::AstNode'
    TYPE_PATTERN = re.compile(rf'\b{NS}::')

    @staticmethod
    def _eval(expr: str):
        def func(valobj: lldb.SBValue, name: str | None = None) -> lldb.SBValue:
            value = dereference_value(valobj)
            ext_args = [lldb.SBExpressionOptions(), name] if name else []
            return value.EvaluateExpression(expr, *ext_args)

        return func

    @staticmethod
    def _ets_source(valobj: lldb.SBValue, name: str | None = None) -> lldb.SBValue:
        value = dereference_value(valobj)
        ext_args = [lldb.SBExpressionOptions(), name] if name else []
        return value.EvaluateExpression('DumpEtsSrc()', *ext_args)

    @staticmethod
    def _is_separate(valobj: lldb.SBValue, name: str | None = None) -> lldb.SBValue:
        value = dereference_value(valobj)
        ext_args = [lldb.SBExpressionOptions(), name] if name else []
        return value.EvaluateExpression('DumpEtsSrc()', *ext_args)

    @staticmethod
    def is_type(sbtype: lldb.SBType, internal_dict):
        with error_ctx():
            if AstNode.TYPE_PATTERN.search(sbtype.GetName()):
                return get_parent_type_by_name(sbtype, AstNode.TYPE_ATS_NODE) is not None
            return False

    @staticmethod
    def synth(valobj: lldb.SBValue, internal_dict):
        with error_ctx():
            return SynthValue(
                valobj,
                [
                    SynthMember('[ets]', AstNode._eval('DumpEtsSrc()')),
                    SynthMember('[sep]', AstNode._eval('range_.program_->IsSeparateModule()')),
                ],
            )

    @staticmethod
    def _get_class_name(sbtype: lldb.SBType) -> str:
        if sbtype.IsPointerType():
            sbtype = sbtype.GetPointeeType()
        return str(sbtype.GetName())

    @staticmethod
    def summary(valobj: lldb.SBValue, internal_dict):
        with error_ctx():
            tname = AstNode._get_class_name(valobj.GetType())
            class_name = tname.split('::')[-1]
            return f'ir::{class_name}{{ {AstNode._ets_source(valobj).GetSummary()} }}'
